package com.touzhijia.authority.security;

import com.touzhijia.authority.domain.entity.Permission;
import com.touzhijia.authority.domain.entity.WebPermission;
import com.touzhijia.authority.domain.model.WebPermissionDto;
import com.touzhijia.authority.mapper.WebPermissionMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDecisionVoter;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.FilterInvocation;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.Collection;
import java.util.Objects;

/**
 * 自定义授权
 * <p>
 * 作者： lzw<br/>
 * 创建时间：2018-03-15 17:51 <br/>
 */
@Component
@Slf4j
public class CustomAccessDecisionVoter implements AccessDecisionVoter<FilterInvocation> {

    @Autowired
    private SecurityConfig securityConfig;
    @Autowired
    private WebPermissionMapper webPermissionMapper;

    @Autowired
    private RequestMappingHandlerMapping requestMappingHandlerMapping;

    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }

    /**
     * 授权实现
     */
    @Override
    public int vote(Authentication authentication, FilterInvocation filterInvocation, Collection<ConfigAttribute> attributes) {
        if (!(authentication instanceof UserLoginToken)) {
            log.info("### 放弃授权(Authentication类型不匹配UserLoginToken) -> [{}]", filterInvocation.getRequestUrl());
            return ACCESS_ABSTAIN;
        }
        UserLoginToken userLoginToken = (UserLoginToken) authentication;
        UserDetails userDetails = userLoginToken.getUserDetails();
        if (userDetails == null) {
            log.info("### 放弃授权(UserDetails为空) -> [{}]", filterInvocation.getRequestUrl());
            return ACCESS_ABSTAIN;
        }
        if (!(userDetails instanceof CustomUserDetails)) {
            log.info("### 放弃授权(UserDetails类型不匹配CustomUserDetails) -> [{}]", filterInvocation.getRequestUrl());
            return ACCESS_ABSTAIN;
        }
        CustomUserDetails customUserDetails = (CustomUserDetails) userDetails;
        log.info("### 开始授权 [username={}] -> [{}]", userLoginToken.getUsername(), filterInvocation.getRequestUrl());
        HandlerExecutionChain handlerExecutionChain = null;
        try {
            handlerExecutionChain = requestMappingHandlerMapping.getHandler(filterInvocation.getHttpRequest());
        } catch (Throwable e) {
            log.warn("### 授权时出现异常", e);
        }
        if (handlerExecutionChain == null) {
            log.info("### 授权通过(未知的资源404) -> [{}]", filterInvocation.getRequestUrl());
            return ACCESS_GRANTED;
        }
        HandlerMethod handlerMethod = (HandlerMethod) handlerExecutionChain.getHandler();
        String controllerClass = handlerMethod.getBeanType().getName();
        String controllerMethod = handlerMethod.getMethod().getName();
        // 获取放签名
        StringBuilder methodParams = new StringBuilder();
        Class<?>[] paramTypes = handlerMethod.getMethod().getParameterTypes();
        for (Class<?> clzz : paramTypes) {
            if (methodParams.length() > 0) {
                methodParams.append(", ");
            }
            methodParams.append(clzz.getName());
        }
        WebPermissionDto webPermissionDto = webPermissionMapper.getUrlPermissionByController(securityConfig.getModuleName(), controllerClass, controllerMethod, methodParams.toString());
        if (webPermissionDto == null) {
            log.info("### 授权通过(当前资源未配置权限) [{}#{}] -> [{}]", controllerClass, controllerMethod, filterInvocation.getRequestUrl());
            return ACCESS_GRANTED;
        }
        log.info("### 权限字符串 [{}] -> [{}]", webPermissionDto.getPermission(), filterInvocation.getRequestUrl());
        if (Objects.equals(webPermissionDto.getNeedAuthorization(), WebPermission.Need_Authorization_2)) {
            log.info("### 授权通过(当前资源不需要访问权限) [{}#{}] [{}] -> [{}]", controllerClass, controllerMethod, webPermissionDto.getResourcesUrl(), filterInvocation.getRequestUrl());
            return ACCESS_GRANTED;
        }
        // 对比权限字符串 permission.getPermission()
        if (checkPermission(webPermissionDto.getPermission(), customUserDetails.getAuthorities())) {
            log.info("### 授权通过(已授权) [{}#{}] [{}] -> [{}]", controllerClass, controllerMethod, webPermissionDto.getResourcesUrl(), filterInvocation.getRequestUrl());
            return ACCESS_GRANTED;
        }
        log.info("### 授权失败(未授权) [{}#{}] [{}] -> [{}]", controllerClass, controllerMethod, webPermissionDto.getResourcesUrl(), filterInvocation.getRequestUrl());
        return ACCESS_DENIED;
    }

    /**
     * 校验权限字符串
     */
    private boolean checkPermission(String permissionStr, Collection<CustomGrantedAuthority> authorities) {
        if (authorities == null) {
            return false;
        }
        for (CustomGrantedAuthority grantedAuthority : authorities) {
            if (Objects.equals(permissionStr, grantedAuthority.getAuthority())) {
                Permission permission = grantedAuthority.getPermission();
                log.info("### 权限字符串匹配成功 [{}] [{}]", permission.getPermission(), permission.getTitle());
                return true;
            }
        }
        return false;
    }
}
